package net.mingsoft.document.biz.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
import com.google.common.io.Files;
import net.mingsoft.base.entity.ResultData;
import net.mingsoft.basic.biz.IManagerBiz;
import net.mingsoft.basic.core.SessionContext;
import net.mingsoft.basic.dao.IManagerDao;
import net.mingsoft.basic.entity.ManagerEntity;
import net.mingsoft.basic.util.*;
import net.mingsoft.document.action.q.*;
import net.mingsoft.document.action.r.*;
import net.mingsoft.document.biz.FileBiz;
import net.mingsoft.document.biz.FileLogBiz;
import net.mingsoft.document.constant.FileConstant;
import net.mingsoft.document.dao.*;
import net.mingsoft.document.entity.*;
import net.mingsoft.document.enums.*;
import net.mingsoft.document.util.FileTypeUtil;
import net.mingsoft.document.util.PermissionUtil;
import net.mingsoft.message_center.biz.MessageBiz;
import net.mingsoft.message_center.entity.MessageCenterEntity;
import net.mingsoft.message_center.enums.MessageType;
import net.mingsoft.people.biz.IPeopleBiz;
import net.mingsoft.people.biz.IPeopleUserBiz;
import net.mingsoft.people.dao.IPeopleDao;
import net.mingsoft.people.entity.PeopleEntity;
import net.mingsoft.zwy.biz.MessageSendBiz;
import net.mingsoft.zwy.biz.q.MsgQuery;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.helpers.MessageFormatter;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * @author gengfa
 * @date 22/01/13
 */
@Service("cmsDocumentBizImpl")
public class FileBizImpl implements FileBiz {
    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(FileBizImpl.class);

    @Resource
    FileDao fileDao;

    @Resource
    FileShareDao fileShareDao;

    @Resource
    FileCodeIncrDao fileCodeIncrDao;

    @Resource
    FileStarDao fileStarDao;

    @Resource
    FileGrantDao fileGrantDao;

    @Resource
    FileLogBiz fileLogBiz;

    @Resource
    MessageBiz messageBiz;

    @Resource
    MessageSendBiz messageSendBiz;

    @Resource
    IPeopleDao iPeopleDao;

    @Resource
    IManagerDao iManagerDao;

    @Value("${ms.document.code.num:3}")
    private int fileCodeSize;

    @Value("${ms.upload.path}")
    private String uploadPath;

    @Value("${ms.upload.storeType:STATIC}")
    private String storageType;

    @Override
    public Page<FileSearchResponse> searchFile(FileSearchQuery query) {
        Page<FileSearchResponse> page = fileDao.selectListByName(
                new Page<>(query.getPageNo(), query.getPageSize()), query);

        page.getRecords().forEach(it -> {
            it.setFilePath(Paths.get(it.getFilePath()).getParent().toString());
        });

        return page;
    }

    @Override
    public Page<FileEntityResponse> list(FileQuery query) {
        query.setFileName(StringUtil.sqlLikeStr(query.getFileName()));
        query.setCreatedUserName(StringUtil.sqlLikeStr(query.getCreatedUserName()));
        query.setParentDirId(getParentId(query.getParentDirId()));
        Page<FileEntityResponse> page = fileDao.selectListResponse(new Page<>(query.getPageNo(), query.getPageSize()), query);
        if (page.getRecords() != null && page.getRecords().size() > 0) {
            Map<String, FileGrantEntity> map = fileGrantDao.selectMapByFileIds(page.getRecords(),
                    SessionContext.session().getIntegerId());
            page.getRecords().forEach(it -> {
                if (map.get(it.getId()) == null) {
                    it.setPermissionResponses(PermissionUtil.DEFAULT_PERM_RESPONSE);
                } else {
                    List<PermissionResponse> perms = PermissionUtil.resolvePermString(
                            map.get(it.getId()).getPerm());
                    it.setPermissionResponses(perms);
                }
            });
        }
        return page;
    }

    @Override
    public List<DirNode> dirTree() {
        List<DirNode> rootNodes = new ArrayList<>();
        DirNode rootNode = new DirNode();
        List<DirNode> dirNodes = new ArrayList<>();
        setDirNode(dirNodes, FileConstant.ROOT_DIR);
        rootNode.setFileId(FileConstant.ROOT_DIR).setLabel(FileConstant.ROOT_DIR_NAME).setChildren(dirNodes);
        rootNodes.add(rootNode);
        return rootNodes;
    }

    @Override
    public Object fileTree(FileTreeQuery query) {

        if (StringUtils.isNotBlank(query.getFileName())) {
            List<FileNode> nodes = new ArrayList<>();
            query.setFileName(SqlUtil.likeStr(query.getFileName()));
            List<FileEntity> entities = fileDao.selectFileListByName(query.getFileName());
            if (CollectionUtils.isNotEmpty(entities)) {
                for (FileEntity entity : entities) {
                    makeFileNode(nodes, entity);;
                }
            }
            return mergeList(nodes);
        } else {
            List<FileNode> nodes = new ArrayList<>();
            if (StringUtils.isBlank(query.getFileId())) {
                query.setFileId(FileConstant.ROOT_DIR);
            }

            List<FileEntity> entities = fileDao.selectFileListByParentId(query.getFileId());
            if (CollectionUtils.isNotEmpty(entities)) {
                for (FileEntity entity : entities) {
                        nodes.add(new FileNode()
                                .setFileId(entity.getId())
                                .setLabel(entity.getFileOriginName())
                                .setChildren(isDir(entity)?new ArrayList<>():null));
                }
            }
            return nodes;
        }
    }

    private List<FileNode> mergeList(List<FileNode> nodes) {
        List<FileNode> newNodes = new ArrayList<>();

        // merge node
        if (CollectionUtils.isNotEmpty(nodes)) {
            for (FileNode node : nodes) {
                final int i = containsName(newNodes, node.getLabel());
                if (i >= 0) {
                    if (newNodes.get(i).getChildren() != null) {
                        newNodes.get(i).getChildren().addAll(node.getChildren());
                    } else {
                        newNodes.add(node);
                    }
                } else {
                    newNodes.add(node);
                }
            }
        }

        // merge children node
        newNodes.forEach(it -> {
            it.setChildren(mergeList(it.getChildren()));
            if (CollectionUtils.isEmpty(it.getChildren())) {
                it.setChildren(null);
            }
        });

        return newNodes;
    }

    private int containsName(List<FileNode> nodes, String name) {
        for (int i = 0; i < nodes.size(); i++) {
            if (StringUtils.equals(name, nodes.get(i).getLabel())) {
                return i;
            }
        }
        return -1;
    }

    private void makeFileNode(List<FileNode> nodes, FileEntity entity) {
        Path path = Paths.get(entity.getFilePath());
        String separator = File.separator;
        if (StringUtils.equals(File.separator, FileConstant.FILE_SEPARATOR_WIN)) {
            separator = FileConstant.FILE_SEPARATOR_WIN_R;
        }
        String[] paths = path.toString().split(separator);
        List<FileNode> fileNodes = makeChildrenNodes(paths, isDir(entity), entity.getId(), entity.getFileOriginName());
        nodes.addAll(fileNodes);
    }

    private List<FileNode> makeChildrenNodes(String[] paths, boolean isDir, String fileId, String fileOriginName) {
        FileNode rootNode = new FileNode().setChildren(new ArrayList<>());
        final FileNode[] lastNode = {rootNode};
        IntStream.range(1, paths.length).forEach(i -> {
                FileNode newNode = new FileNode().setFileId(UniqueCodeUtil.genCode()).setLabel(paths[i]).setChildren(new ArrayList<>());
                lastNode[0].getChildren().add(newNode);
                lastNode[0] = newNode;
            });
        lastNode[0].setFileId(fileId).setChildren(isDir?new ArrayList<>():null).setLabel(fileOriginName);
        return rootNode.getChildren();
    }

    private void setDirNode(List<DirNode> nodes, String parentDir) {
        List<FileEntity> entities = fileDao.selectListByParentId(parentDir);
        if (CollectionUtils.isNotEmpty(entities)) {
            entities.forEach(it -> {
                DirNode node = new DirNode()
                        .setFileId(it.getId())
                        .setChildren(new ArrayList<>())
                        .setLabel(it.getFileOriginName());
                nodes.add(node);
                setDirNode(node.getChildren(), it.getId());
            });
        }
    }

    @Override
    public List<FileEntity> dirList(DirListQuery query) {
        return new ArrayList<>();
    }

    @Override
    public Page<FileLogEntity> fileLog(FileLogQuery query) {
        return fileLogBiz.list(query);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ModifyResponse modify(FileModifyQuery query) {
        ModifyResponse modifyResponse = new ModifyResponse().setFailedType("OTHER").setModified(false);
        FileEntity fileEntity = getFileEntity(query.getId());
        if (fileEntity != null) {
            boolean isSameCode = StringUtils.equals(fileEntity.getFileCode(), query.getFileCode());
            boolean isSameName = StringUtils.equals(fileEntity.getFileOriginName(), query.getFileName());
            if (isSameCode && !isSameName) {
                FileEntity nameEntity = fileDao.selectExistSameName(query.getFileName(), fileEntity.getId(), fileEntity.getParentDir());
                if (nameEntity != null) {
                    modifyResponse.setFailedType("NAME").setModified(false);
                } else {
                    modifyResponse.setModified(true);
                }
            } else if (!isSameCode && isSameName) {
                FileEntity codeEntity = fileDao.selectExistSameCode(query.getFileCode(), fileEntity.getId(), fileEntity.getParentDir());
                if (codeEntity != null) {
                    modifyResponse.setFailedType("CODE").setModified(false);
                } else {
                    modifyResponse.setModified(true);
                }
            } else if (!isSameCode) {
                FileEntity nameEntity = fileDao.selectExistSameName(query.getFileName(), fileEntity.getId(), fileEntity.getParentDir());
                FileEntity codeEntity = fileDao.selectExistSameCode(query.getFileCode(), fileEntity.getId(), fileEntity.getParentDir());
                if (nameEntity != null && codeEntity != null) {
                    modifyResponse.setFailedType("ALL").setModified(false);
                } else if (nameEntity != null) {
                    modifyResponse.setFailedType("NAME").setModified(false);
                } else if (codeEntity != null) {
                    modifyResponse.setFailedType("CODE").setModified(false);
                } else {
                    modifyResponse.setModified(true);
                }
            } else {
                modifyResponse.setFailedType("ALL").setModified(false);
            }

            if (modifyResponse.isModified() && 1 == fileDao.updateFileNameAndFileCode(query)) {
                if (isDir(fileEntity)) {
                    try {
                        String parentPath = Paths.get(fileEntity.getFilePath()).getParent().toString();
                        String dirFilePath = parentPath;
                        if (StringUtils.length(parentPath) == 1) {
                            dirFilePath += query.getFileName();
                        } else {
                            dirFilePath += (File.separator + query.getFileName());
                        }
                        if (1 != fileDao.updateFilePath(fileEntity.getId(), dirFilePath)) {
                            throw new Exception("modify update failed");
                        } else {
                            boolean b = new File(makeRealPath(fileEntity.getFilePath())).renameTo(new File(makeRealPath(dirFilePath)));
                            if (!b) {
                                throw new Exception("modify real dir failed");
                            } else {
                                if (!updateChildrenFile(fileEntity.getId(), fileEntity.getFilePath(), dirFilePath)) {
                                    throw new Exception("modify children files failed");
                                }
                            }
                        }
                    } catch (Exception e) {
                        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                        log.error(e.getMessage());
                    }
                }
                modifyResponse.setFailedType("ALL").setModified(true);
            }
        }

        return modifyResponse;
    }

    public boolean updateChildrenFile(String parentId, String oldFilePath, String newFilePath) {
        List<FileEntity> childrenIds = getChildren(parentId);
        childrenIds.forEach(it -> {
            final String filePath = Paths.get(it.getFilePath()).toString();
            final String oPath = Paths.get(oldFilePath).toString();
            final String nPath = Paths.get(newFilePath).toString();
            final String newPath = nPath + StringUtils.substring(filePath, oPath.length());
            it.setFilePath(newPath).setFileUrl(newPath);
        });

        int effectRows = 0;
        for (FileEntity childrenFile : childrenIds) {
            effectRows += fileDao.updateFilePath(childrenFile.getId(), childrenFile.getFilePath());
        }

        return effectRows == childrenIds.size();
    }

    public List<FileEntity> getChildren(String parentId) {
        List<FileEntity> children = new ArrayList<>();
        List<FileEntity> entities = fileDao.selectFileListByParentId(parentId);
        if (CollectionUtils.isNotEmpty(entities)) {
            children.addAll(entities);
            for (FileEntity entity : entities) {
                if (isDir(entity)) {
                    children.addAll(getChildren(entity.getId()));
                }
            }
        }
        return children;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean delete(List<String> fileIds) {
        if (CollectionUtils.isNotEmpty(fileIds)) {
            List<FileEntity> entities = fileDao.selectListByIds(fileIds);
            // tag
            entities.forEach(it -> {
                try {
                    fileDao.updateStatus(it.getId(), FileStatus.DELETED.getStatus());
                    fileLogBiz.record(it.getId(), FileOperateType.DELETE);
                    List<Long> watchers = getWatchers(it.getId());
                    if (CollectionUtils.isNotEmpty(watchers)) {
                        for (Long watcher : watchers) {
                            messageBiz.add(
                                    makeMessage(watcher, it, MessageType.STAR_WATCH,
                                            FileOperateType.DELETE, DFileType.getDesc(it.getFileType()))
                            );
                        }
                    }
                } catch (Exception e) {
                    log.error("delete file failed: {}", e.getMessage());
                    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                }
            });

            // moved
            entities.forEach(it -> {
                try {
                    if (StringUtils.equals(DFileType.DIR.getType(), it.getFileType())) {
                        FileUtils.moveDirectory(
                                new File(uploadPath + File.separator + FileConstant.DOCUMENT_PATH_PREFIX + it.getFilePath()),
                                new File(uploadPath + File.separator + FileConstant.DOCUMENT_PATH_PREFIX + it.getFilePath() + FileConstant.DELETED_SUFFIX));
                    } else {
                        FileUtils.moveFile(
                                new File(uploadPath + File.separator + FileConstant.DOCUMENT_PATH_PREFIX + it.getFilePath()),
                                new File(uploadPath + File.separator + FileConstant.DOCUMENT_PATH_PREFIX + it.getFilePath() + FileConstant.DELETED_SUFFIX));
                    }
                } catch (Exception e) {
                    log.error("delete file failed: {}", e.getMessage());
                    //TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                }
            });

            return true;
        }
        return false;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public MakeDirectoryResponse mkdir(DirQuery query) {
        MakeDirectoryResponse response = new MakeDirectoryResponse();

        try {
            String filePath = getParentPath(query.getParentDirId());
            String dirName = makeDirectoryName(query.getParentDirId());
            query.setDirName(dirName);

            FileUtils.forceMkdir(
                    new File(uploadPath + File.separator + FileConstant.DOCUMENT_PATH_PREFIX
                            + filePath + File.separator + query.getDirName())
            );

            FileEntity entity = makeDirEntity(query, filePath);
            FileGrantEntity grantEntity = makeGrantEntity(entity);
            if (1 == fileDao.insertDir(entity) && 1 == fileGrantDao.insertOne(grantEntity)) {
                fileLogBiz.record(query.getParentDirId(), FileOperateType.CREATE);
                response.setId(entity.getId());
                response.setDirName(dirName);
                response.setParentDirId(entity.getParentDir());
                return response;
            } else {
                throw new Exception("insert failed");
            }
        } catch (Exception e) {
            log.error("make directory failed: {}", e.toString());
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }

        return response;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public ShareResponse share(FileShareQuery query) {
        ShareResponse response = new ShareResponse();

        try {
            FileEntity fileEntity = getFileEntity(query.getFileId());

            for (Long userId : query.getUserIds()) {

                FileShareEntity entity = new FileShareEntity()
                        .setFileId(query.getFileId())
                        .setShareBy(Long.parseLong(SessionContext.session().getId()))
                        .setShareUsername(SessionContext.session().getManagerNickName())
                        .setReceiver(userId)
                        .setReceiverUsername(null)
                        .setShareExpired(query.getExpiredDate())
                        .setShareLink(FileConstant.DEFAULT_SHARE_LINK)
                        .setSharePwd(StringUtils.isNotBlank(query.getPassword()) ? query.getPassword() : null)
                        .setCreatedBy(SessionContext.session().getId())
                        .setCreatedDate(DateUtil.now());

                if (1 == fileShareDao.insertOne(entity)) {
                    fileLogBiz.record(query.getFileId(), FileOperateType.SHARE);

                    // message to {userId}
                    messageBiz.add(
                            makeMessage(userId, fileEntity, MessageType.SHARE, FileOperateType.SHARE)
                    );

                    response.setFileId(query.getFileId())
                            .setShareLink(entity.getShareLink())
                            .setSharePwd(query.getPassword())
                            .setShareWith(userId);
                } else {
                    throw new Exception("insert failed");
                }

                // TODO announcement
                if (query.getAnnouncementType() != null && query.getAnnouncementType().contains("SMS")) {
                    if (query.getUserIds() != null && query.getUserIds().size() > 0) {
                        List<ManagerEntity> managerEntities = iManagerDao.selectByIds(query.getUserIds());
                        if (CollectionUtils.isNotEmpty(managerEntities)) {
                            List<Integer> peopleIds = managerEntities.stream().map(ManagerEntity::getPeopleId).collect(Collectors.toList());
                            List<PeopleEntity> peopleEntities = iPeopleDao.getByPeopleIds(peopleIds);
                            if (CollectionUtils.isNotEmpty(peopleEntities)) {
                                peopleEntities.forEach(o -> {
                                    messageSendBiz.sendMsg(new MsgQuery().setMobiles(o.getPeoplePhone()).setContent(FileOperateType.SHARE.getMessageContent()));
                                });
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }


        return response;
    }

    @Override
    public void download(FileDownloadQuery query, HttpServletResponse response) {
        FileEntity entity = fileDao.selectOne(query.getId(), null);
        if (entity != null) {
            String realPath = makeRealPath(entity.getFilePath());
            try {
                if (StringUtils.equals(entity.getFileType(), DFileType.DOC.getType())) {
                    byte[] bytes = FileUtils.readFileToByteArray(
                            new File(realPath));
                    response.setContentType("application/octet-stream");
                    response.setHeader("Content-disposition", "attachment; " +
                            "filename=" + URLEncoder.encode(entity.getFileOriginName(), StandardCharsets.UTF_8.toString()));
                    response.getOutputStream().write(bytes);
                } else if (StringUtils.equals(entity.getFileType(), DFileType.DIR.getType())) {
                    response.setContentType("application/zip");
                    response.setHeader("Content-disposition", "attachment; " +
                            "filename=" + URLEncoder.encode(entity.getFileOriginName(), StandardCharsets.UTF_8.toString()) + ".zip");
                    ZipUtil.compressZipFile(realPath, response.getOutputStream());
                }
                fileLogBiz.record(query.getId(), FileOperateType.DOWNLOAD);
            } catch (IOException ioException) {
                ioException.printStackTrace();
                log.error("download file failed");
            }
        }
    }

    @Override
    public byte[] preview(FilePreviewQuery query) {
        return new byte[0];
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean grant(FileGrantQuery query) {
        String fileType = fileDao.selectFileTypeById(query.getFileId());
        String perm = PermissionUtil.genFilePerm(query.getGrantTypes(), fileType);
        try {
            for (Long userId : query.getUserIds()) {
                FileGrantEntity entity = new FileGrantEntity()
                        .setFileId(query.getFileId())
                        .setPerm(perm)
                        .setUserId(userId)
                        .setCreatedBy(SessionContext.session().getId())
                        .setCreatedDate(DateUtil.now());
                if (1 == fileGrantDao.insertOne(entity)) {
                    fileLogBiz.record(entity.getFileId(), FileOperateType.GRANT);
                } else {
                    throw new Exception("grant file failed");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return false;
        }
        return true;
    }

    private FileGrantEntity makeGrantEntity(FileEntity entity) {
        return new FileGrantEntity()
                .setFileId(entity.getId())
                .setUserId(SessionContext.session().getIntegerId())
                .setPerm(StringUtils.equals(entity.getFileType(), DFileType.DIR.getType()) ? PermissionUtil.ALL_EXT_PERM_DIR : PermissionUtil.ALL_EXT_PERM_DOC)
                .setCreatedBy(SessionContext.session().getId())
                .setCreatedDate(DateUtil.now());
    }

    @Override
    public PermissionWrRdResponse permission(String fileId) {

        List<PermissionResponse> list;
        List<PermissionResponse> listWr;

        FileEntity entity = getFileEntity(fileId);
        FileGrantEntity fileGrantEntity = fileGrantDao.selectByFileId(fileId);

        List<PermissionResponse> responses = PermissionUtil.resolvePermString(fileGrantEntity.getPerm());

        if (isDir(entity)) {
            responses.remove(1);
            list = responses.subList(0, 1);
            listWr = responses.subList(1, responses.size());
        } else {
            responses.remove(2);
            list = responses.subList(0, 2);
            listWr = responses.subList(2, responses.size());
        }

        return new PermissionWrRdResponse().setRead(list).setWrite(listWr);
    }

    @Override
    public boolean star(FileStarQuery query) {
        FileStarEntity entity = new FileStarEntity()
                .setFileId(query.getFileId())
                .setUserId(Integer.parseInt(SessionContext.session().getId()))
                .setIsWatched(query.isWatched() ? 1 : 0)
                .setCollectionId(query.getStarFolderId())
                .setCreatedBy(SessionContext.session().getId())
                .setCreatedDate(DateUtil.now());
        return 1 == fileStarDao.insertOne(entity);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public MoveCopyResponse moveCopy(FileMoveAndCopyQuery query) {
        MoveCopyResponse response = new MoveCopyResponse();

        FileEntity sourceEntity = getFileEntity(query.getSourceFileId());
        FileEntity targetEntity;
        if (StringUtils.equals(query.getTargetFileId(), FileConstant.ROOT_DIR)) {
            targetEntity = new FileEntity().setFilePath(FileConstant.BLANK_FILE_PATH).setId(FileConstant.ROOT_DIR);
        } else {
            targetEntity = getFileEntity(query.getTargetFileId());
        }

        try {
            // 移动文件
            if (StringUtils.equals(query.getOperateType(), FileOperateType.MV.getType())) {
                String sourceFileParentPath = Paths.get(sourceEntity.getFilePath()).getParent().toString();
                String targetPath = Paths.get(targetEntity.getFilePath()).toString();

                if (StringUtils.equals(sourceFileParentPath, targetPath) ||
                        (StringUtils.equals(sourceFileParentPath, targetPath) && StringUtils.equals(sourceFileParentPath, File.separator) )) {
                    response.setOp(false);
                } else {
                    moveFile(query, sourceEntity, targetEntity, response);
                }
            }

            // 复制文件
            if (StringUtils.equals(query.getOperateType(), FileOperateType.CP.getType())) {
                if (StringUtils.equals(sourceEntity.getId(), targetEntity.getId())) {
                    response.setOp(false);
                } else {
                    copyFile(sourceEntity, targetEntity, response);
                }
            }
        } catch (Exception e) {
            log.error("move/copy file failed");
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            e.printStackTrace();
            response.setOp(false);
        }

        return response;
    }

    private void moveFile(FileMoveAndCopyQuery query,
                          FileEntity sourceEntity,
                          FileEntity targetEntity,
                          MoveCopyResponse response) throws Exception {
        String newSourceFilePath;
        boolean updated;
        if (!isDir(query.getSourceFileId())) {
            newSourceFilePath = targetEntity.getFilePath() + File.separator + sourceEntity.getFileName();
            if (updated = (1 == fileDao.updateParentDir(query.getSourceFileId(), query.getTargetFileId(),
                    newSourceFilePath, newSourceFilePath, null))) {
                FileUtils.moveFileToDirectory(
                        new File(makeRealPath(sourceEntity.getFilePath())),
                        new File(makeRealPath(targetEntity.getFilePath())),
                        false
                );
            }
        } else {
            String newDirName = sourceEntity.getFileOriginName();
            if (hasSameDirectory(sourceEntity, targetEntity)) {
                newDirName = sourceEntity.getFileOriginName() + "_" + DateUtil.nowMills();
                newSourceFilePath = targetEntity.getFilePath() + File.separator + newDirName;
            } else {
                newSourceFilePath = targetEntity.getFilePath() + File.separator + sourceEntity.getFileOriginName();
            }
            if (updated = (1 == fileDao.updateParentDir(query.getSourceFileId(), query.getTargetFileId(),
                    newSourceFilePath, newSourceFilePath, newDirName))) {
                FileUtils.moveDirectory(
                        new File(makeRealPath(sourceEntity.getFilePath())),
                        new File(makeRealPath(newSourceFilePath))
                );
                updateChildren(sourceEntity.getId(), sourceEntity.getFilePath(), newSourceFilePath);
            }
        }

        if (!updated) {
            throw new Exception("move file failed");
        }

        //sendStarWatchMessage(sourceEntity, targetEntity, FileOperateType.MOVE);

        response.setOp(true).setNewFilePath(newSourceFilePath).setNewFileId(sourceEntity.getId());
    }

    public boolean updateChildren(String parentId, String oldFilePath, String newFilePath) {
        List<FileEntity> childrenIds = getChildren(parentId);

        final String oPath = Paths.get(oldFilePath).toString();
        final String nPath = Paths.get(newFilePath).toString();

        int effectRows = 0;
        for (FileEntity it : childrenIds) {
            final String filePath = Paths.get(it.getFilePath()).toString();
            final String newPath = nPath + StringUtils.substring(filePath, oPath.length());
            final int ef = fileDao.updateFilePath(it.getId(), newPath);
            if (!isDir(it)) {
                effectRows += ef;
                //sendStarWatchMessage(it, new FileEntity().setFilePath(newPath), FileOperateType.MOVE);
            }
        }

        return effectRows == childrenIds.size();
    }

    private void copyFile(FileEntity sourceEntity, FileEntity targetEntity, MoveCopyResponse response) throws Exception {
        // generate new file
        FileEntity newEntity = new FileEntity();
        BeanUtils.copyProperties(sourceEntity, newEntity);

        // addition options
        String uniqueCode = UniqueCodeUtil.genCode();
        String copyToPath = Paths.get(targetEntity.getFilePath()).toString();
        String destFilePath, newFileName, newFilePath, fileType, dirName = null;

        if (isDir(sourceEntity)) {
            fileType = DFileType.DIR.getType();
            dirName = sourceEntity.getFileOriginName();
            if (hasSameDirectory(sourceEntity, targetEntity)) {
                dirName += ("_" + DateUtil.nowMills());
            }
            newEntity.setFileOriginName(dirName);
            destFilePath = copyToPath + File.separator + dirName;
            newFilePath = destFilePath;
            newFileName = uniqueCode;
        } else {
            // destFilePath = copyToPath + File.separator + sourceEntity.getFileName();
            fileType = DFileType.DOC.getType();
            @SuppressWarnings("all")
            String fileExt = Files.getFileExtension(sourceEntity.getFileOriginName());
            newFilePath = copyToPath + File.separator + uniqueCode + FileConstant.DOT + fileExt;
            newFileName = uniqueCode + FileConstant.DOT + fileExt;
        }

        newEntity.setFilePath(newFilePath).setFileUrl(newFilePath)
                .setId(uniqueCode).setCreatedDate(DateUtil.now())
                .setFileCode(makeFileCode()).setCreatedBy(SessionContext.session().getId())
                .setParentDir(targetEntity.getId())
                .setFileName(newFileName)
                .setFileType(fileType);
        FileGrantEntity grantEntity = makeGrantEntity(newEntity);
        if (1 == fileDao.insertOne(newEntity) && 1 == fileGrantDao.insertOne(grantEntity)) {
            if (isDir(sourceEntity)) {
                FileUtils.copyDirectory(
                        new File(makeRealPath(sourceEntity.getFilePath())),
                        new File(makeRealPath(newFilePath))
                );
                updateCopiedChildren(sourceEntity.getId(), newEntity.getId(), dirName, targetEntity.getFilePath());
            } else {
                FileUtils.copyFile(
                        new File(makeRealPath(sourceEntity.getFilePath())),
                        new File(makeRealPath(newFilePath))
                );
            }

            /*boolean isCopied = new File(makeRealPath(destFilePath))
                    .renameTo(new File(makeRealPath(newFilePath)));

            if (!isCopied) {
                throw new Exception("copy file failed");
            }*/

            response.setOp(true)
                    .setNewFileId(newEntity.getId())
                    .setNewFilePath(newEntity.getFilePath());

            //sendStarWatchMessage(sourceEntity, targetEntity, FileOperateType.COPY);
        } else {
            throw new Exception("copy file failed");
        }
    }

    private boolean hasSameDirectory(FileEntity sourceEntity, FileEntity targetEntity) {
        List<FileEntity> files = fileDao.selectFileListByParentId(targetEntity.getId());
        for (FileEntity f : files) {
            if (StringUtils.equals(f.getFileOriginName(), sourceEntity.getFileOriginName())) {
                return true;
            }
        }
        return false;
    }

    private void updateCopiedChildren(String parentFileId, String targetParentId, String sourceDirName, String parentPath) throws Exception {
        // all children files had moved to target directory, so we just need correct the db data and target filepath
        // files in source directory
        List<FileEntity> entities = fileDao.selectFileListByParentId(parentFileId);
        // foreach and insert new entity
        for (FileEntity entity : entities) {
            // new basic entity
            String uniqueCode = UniqueCodeUtil.genCode();
            FileEntity newEntity = new FileEntity();
            BeanUtils.copyProperties(entity, newEntity);
            newEntity.setId(uniqueCode)
                    .setCreatedBy(SessionContext.session().getId())
                    .setCreatedDate(DateUtil.now())
                    .setCreatedUserName(SessionContext.session().getManagerNickName())
                    .setParentDir(targetParentId);

            if (!isDir(entity)) {
                // is file
                newEntity.setFileCode(makeFileCode())
                        .setFileName(uniqueCode + FileConstant.DOT + newEntity.getFileExt());
                newEntity.setFilePath(PlatformJudgeUtil.reSlash(
                        parentPath + File.separator + sourceDirName + File.separator + newEntity.getFileName()
                ));
                String movedPath = PlatformJudgeUtil.reSlash(
                        parentPath + File.separator + sourceDirName + File.separator + entity.getFileName()
                );
                FileGrantEntity grantEntity = makeGrantEntity(newEntity);
                if (1 == fileDao.insertOne(newEntity) && 1 == fileGrantDao.insertOne(grantEntity)) {
                    boolean b = new File(makeRealPath(movedPath)).renameTo(new File(makeRealPath(newEntity.getFilePath())));
                    if (!b) {
                        throw new Exception("copied failed");
                    }
                    //sendStarWatchMessage(entity, newEntity, FileOperateType.COPY);
                }
            } else {
                // is directory
                // set new filepath and filename
                newEntity.setFileName(uniqueCode)
                        .setFilePath(parentPath + File.separator + newEntity.getFileOriginName());
                FileGrantEntity grantEntity = makeGrantEntity(newEntity);
                if (1 == fileDao.insertDir(newEntity) && 1 == fileGrantDao.insertOne(grantEntity)) {
                    updateCopiedChildren(entity.getId(), newEntity.getId(),
                            entity.getFileOriginName(), newEntity.getFilePath());
                } else {
                    throw new Exception("copied failed");
                }
            }
        }
    }

    private boolean isDir(String id) {
        return StringUtils.equals(fileDao.selectFileTypeById(id), DFileType.DIR.getType());
    }

    private boolean isDir(FileEntity entity) {
        return StringUtils.equals(entity.getFileType(), DFileType.DIR.getType());
    }

    private FileEntity getFileEntity(String id) {
        return fileDao.selectOne(id, null);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResultData uploadMultiFiles(UploadFileQuery query) {
        try {
            String filePath = getParentPath(query.getParentDirId());

            List<UploadResponse> responses = new ArrayList<>();

            FileEntity entity = makeEntity(query.getFile(), query, filePath);
            if (StringUtils.equals(storageType, StorageType.STATIC.getType())) {
                String realPath = uploadPath + File.separator + FileConstant.DOCUMENT_PATH_PREFIX + entity.getFilePath();
                File f = new File(realPath);
                FileUtils.writeByteArrayToFile(f, query.getFile().getBytes());
                entity.setFileUrl(entity.getFilePath());
                FileGrantEntity grantEntity = makeGrantEntity(entity);
                if (1 == fileDao.insertOne(entity) && 1 == fileGrantDao.insertOne(grantEntity)) {
                    responses.add(makeResponse(entity));
                } else {
                    throw new Exception("insert failed");
                }
            }
            fileLogBiz.record(entity.getId(), FileOperateType.CREATE);
            return ResultData.build().success(responses);
        } catch (Exception e) {
            log.error("upload files failed: {}", e.toString());
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return ResultData.build().error(e.toString());
        }
    }

    private UploadResponse makeResponse(FileEntity entity) {
        UploadResponse uploadResponse = new UploadResponse();
        BeanUtils.copyProperties(entity, uploadResponse);
        uploadResponse.setFileUrl(FileConstant.SLASH + FileConstant.DOCUMENT_PATH_PREFIX + uploadResponse.getFileUrl());
        uploadResponse.setFilePath(FileConstant.SLASH + FileConstant.DOCUMENT_PATH_PREFIX + uploadResponse.getFilePath());
        return uploadResponse;
    }

    private String makeRealPath(String path) {
        return uploadPath + File.separator + FileConstant.DOCUMENT_PATH_PREFIX + path;
    }

    private String getParentPath(String id) {
        String filePath;
        if (StringUtils.isNotBlank(id)) {
            if (StringUtils.equals(id, FileConstant.ROOT_DIR)) {
                filePath = FileConstant.BLANK_FILE_PATH;
            } else {
                FileEntity parentDirE = fileDao.selectOne(id, DFileType.DIR.getType());
                if (parentDirE != null) {
                    filePath = parentDirE.getFilePath();
                } else {
                    filePath = FileConstant.BLANK_FILE_PATH;
                }
            }
        } else {
            filePath = FileConstant.BLANK_FILE_PATH;
        }
        return filePath;
    }

    private String getParentId(String id) {
        if (StringUtils.isBlank(id)) {
            return FileConstant.ROOT_DIR;
        }
        return id;
    }

    /**
     * 构造文件目录
     *
     * @param query    查询参数
     * @param filePath 文件路径
     * @return 文件实体
     */
    private FileEntity makeDirEntity(DirQuery query, String filePath) {
        FileEntity entity = new FileEntity();
        entity.setId(UniqueCodeUtil.genCode());
        entity.setFileOriginName(query.getDirName());
        entity.setFileName(entity.getId());
        entity.setFileType(DFileType.DIR.getType());

        entity.setFilePath(PlatformJudgeUtil.reSlash(
                filePath + File.separator + entity.getFileOriginName()
        ));

        entity.setCreatedBy(SessionContext.session().getId());
        entity.setCreatedUserName(SessionContext.session().getManagerNickName());
        entity.setCreatedDate(DateUtil.now());
        entity.setParentDir(getParentId(query.getParentDirId()));
        entity.setFileCode(makeFileCode());
        return entity;
    }

    /**
     * 构造文件实体
     *
     * @param file     文件
     * @param query    上传查询阐述
     * @param filePath 文件路径
     * @return 实体
     */
    private FileEntity makeEntity(MultipartFile file, UploadFileQuery query, String filePath) {
        FileEntity entity = new FileEntity();
        entity.setId(UniqueCodeUtil.genCode());
        entity.setFileOriginName(file.getOriginalFilename());
        entity.setFileExt(matchFileExt(file.getOriginalFilename()));
        entity.setFileName(entity.getId() + FileConstant.DOT + entity.getFileExt());
        entity.setFileCode(makeFileCode());
        entity.setFileType(matchType(entity.getFileExt()));

        entity.setCreatedBy(SessionContext.session().getId());
        entity.setCreatedUserName(SessionContext.session().getManagerNickName());
        entity.setCreatedDate(DateUtil.now());
        entity.setParentDir(getParentId(query.getParentDirId()));

        entity.setFilePath(PlatformJudgeUtil.reSlash(
                filePath + File.separator + entity.getFileName()
        ));
        return entity;
    }

    private synchronized String makeFileCode() {
        String today = DateUtil.nowDate();
        String todayL = DateUtil.nowDateWithoutL();
        FileCodeIncrEntity entity = fileCodeIncrDao.selectByDate(today);
        if (entity == null) {
            entity = new FileCodeIncrEntity();
            entity.setDate(today);
            entity.setCreatedBy("sys");
            entity.setIncrement(1);
            entity.setType(IncrCodeType.FILE.getType());
            entity.setCreatedDate(DateUtil.now());
            fileCodeIncrDao.insertOne(entity);
            return todayL + FileConstant.DEFAULT_FILE_CODE_SUFFIX;
        }
        fileCodeIncrDao.increase(entity.getId());
        return todayL + StringUtil.paddingZero(entity.getIncrement() + 1, fileCodeSize);
    }

    private synchronized String makeDirectoryName(String parentId) {
        /*String today = DateUtil.nowDate();
        FileCodeIncrEntity entity = fileCodeIncrDao.selectDirNumByDate(today);*/
        final List<FileEntity> entities = fileDao.selectListByParentId(parentId);

        int maxIncr = 0;
        for (FileEntity e : entities) {
            if (e.getFileOriginName().startsWith(FileConstant.DEFAULT_NEW_DIR_NAME)) {
                String incrStr = e.getFileOriginName().replace(FileConstant.DEFAULT_NEW_DIR_NAME, FileConstant.BLANK_FILE_PATH);
                int incr;
                try {
                    incr = Integer.parseInt(incrStr);
                } catch (Exception exception) {
                    incr = 0;
                }
                if (maxIncr < incr) {
                    maxIncr = incr;
                }
            }
        }

        if (maxIncr <= 0) {
            return FileConstant.DEFAULT_NEW_DIR_NAME + 1;
        } else {
            return FileConstant.DEFAULT_NEW_DIR_NAME + (maxIncr + 1);
        }

        /*if (entity == null) {
            entity = new FileCodeIncrEntity();
            entity.setDate(today);
            entity.setCreatedBy("sys");
            entity.setIncrement(1);
            entity.setType(IncrCodeType.DIR.getType());
            entity.setCreatedDate(DateUtil.now());
            fileCodeIncrDao.insertOne(entity);
            return FileConstant.DEFAULT_NEW_DIR_NAME;
        }

        fileCodeIncrDao.increaseDir(entity.getId());
        return FileConstant.DEFAULT_NEW_DIR_NAME + entity.getIncrement();*/
    }

    private String matchType(String ext) {
        // TODO
        return FileConstant.DEFAULT_FILE_TYPE;
    }

    private String matchFileExt(String originalName) {
        if (StringUtils.contains(originalName, FileConstant.DOT) && !originalName.endsWith(FileConstant.DOT)) {
            return originalName.substring(originalName.lastIndexOf(FileConstant.DOT) + 1);
        }
        return FileConstant.UNKNOWN_EXT;
    }

    private MessageCenterEntity makeMessage(long receiver, FileEntity fileEntity,
                                            MessageType messageType,
                                            FileOperateType fileOperateType,
                                            String... contents) {
        String now = DateUtil.now();
        return new MessageCenterEntity()
                .setFileOrReportName(fileEntity.getFileOriginName())
                .setMessageType(messageType.getType())
                .setMessageContent(FileOperateType.getMessageContent(fileOperateType, contents))
                .setOperateName(SessionContext.session().getManagerName())
                .setOperatorId(SessionContext.session().getIntegerId())
                .setReceivedDate(now)
                .setReceiver(receiver)
                .setCreatedBy(SessionContext.session().getId())
                .setCreatedDate(now);
    }

    private List<Long> getWatchers(String fileId) {
        return fileStarDao.selectWatchersByFileId(fileId);
    }

    private void sendStarWatchMessage(FileEntity source, FileEntity target, FileOperateType fileOperateType) {
        List<Long> watchers = getWatchers(source.getId());
        Path messageContentPath = Paths.get(target.getFilePath());
        String messageContent;
        if (StringUtils.isBlank(messageContentPath.toString())) {
            messageContent = File.separator;
        } else {
            messageContent = messageContentPath.toString();
        }
        if (CollectionUtils.isNotEmpty(watchers)) {
            for (Long watcher : watchers) {
                messageBiz.add(
                        makeMessage(watcher, source, MessageType.STAR_WATCH,
                                fileOperateType, DFileType.getDesc(source.getFileType()), messageContent)
                );
            }
        }
    }
}
